/**
 * ReportGear(2011)
 */
package com.reportgear.report.model.calculator;

import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import net.abbp.report.model.Cell;
import net.abbp.report.model.ReportModel;
import net.abbp.report.view.cell.value.FormulaValue;

import net.abbp.core.util.StringUtils;
import com.reportgear.report.model.auxiliary.ColumnRow;
import com.reportgear.report.model.script.Expression;
import com.reportgear.report.model.script.ScriptContext;
import com.reportgear.report.model.script.exception.DeathCycleException;

/**
 * 
 * 公式计算
 * 
 * @version 1.0 2011-4-10
 * @author <a herf="lexloo@gmail.com">lexloo</a>
 * @since Report 1.0
 */
public class FormulaCalculator extends AbstractCalculator {
    // 单元格计算状态
    private boolean[][] cellStates;
    // 单元格计算队列，死循环判断
    private List<ColumnRow> executingColumnrowList = new ArrayList<ColumnRow>();
    // 单元格引用检测
    private static Pattern cellPattern = Pattern.compile("([A-Z]+[1-9][0-9]*)+?");
    // 表达式计算
    private Expression expr = ScriptContext.INSTANCE.getExpression();

    // TODO 当前不支持动态参数计算
    public FormulaCalculator() {

    }

    @Override
    public ReportModel calculate(ReportModel model, CalcParameter parameter) {
        if (logger.isDebugEnabled()) {
            logger.debug("计算模型：公式计算");
        }

        ReportModel calcModel = model;

        this.doCalculate(calcModel, parameter.getSessionInfo());

        return calcModel;
    }

    /**
     * 计算
     * 
     * @param model
     *            模型
     * @param sessionInfo
     *            session信息
     */
    private void doCalculate(ReportModel model, SessionInfo sessionInfo) {
        ScriptContext.INSTANCE.setCalcReportModel(model);

        this.cellStates = new boolean[model.getColumnCount()][model.getRowCount()];

        for (Iterator<Cell> i = model.getCellIterator(); i.hasNext();) {
            this.calculateCell(model, i.next(), sessionInfo);
        }

        ScriptContext.INSTANCE.setCalcReportModel(null);
    }

    /**
     * 计算单元格
     * 
     * @param model
     *            模型
     * @param cell
     *            单元格
     * @param sessionInfo
     *            session信息
     */
    private void calculateCell(ReportModel model, Cell cell, SessionInfo sessionInfo) {
        if (cell == Cell.EMPTY_CELL || cell.getValue() == null) {
            return;
        }

        Object value = cell.getValue();

        if (!(value instanceof FormulaValue)) {
            return;
        }

        if (this.cellStates[cell.getColumn()][cell.getRow()]) {
            return;
        }

        ColumnRow cr = ColumnRow.valueOf(cell.getColumn(), cell.getRow());
        if (this.executingColumnrowList.contains(cr)) {
            throw new DeathCycleException("检测到死循环:" + this.executingColumnrowList);
        }

        this.executingColumnrowList.add(cr);

        // 公式中可能引用其他单元格，如果没有计算，先计算
        String content = StringUtils.convertToString(((FormulaValue) value).getFormula());
        content = this.updateSessionInfo(content, sessionInfo);
        Matcher m = cellPattern.matcher(content);
        while (m.find()) {
            ColumnRow crc = ColumnRow.valueOf(m.group(1));
            calculateCell(model, model.getCell(crc.getColumn(), crc.getRow()), sessionInfo);
        }

        cell.setValue(String.valueOf(expr.eval(content)));

        this.executingColumnrowList.remove(cr);
        this.cellStates[cr.getColumn()][cr.getRow()] = true;
    }

    /**
     * 替换SESSION变量
     * 
     * @param content
     * @param sessionInfo
     * @return 更新
     */
    private String updateSessionInfo(String content, SessionInfo sessionInfo) {
        String result = content.replaceAll("\\$\\$BRANCH", "\"" + sessionInfo.getBranchName() + "\"");
        result = result.replaceAll("\\$\\$ORGANIZATION", "\"" + sessionInfo.getOrganization() + "\"");
        result = result.replaceAll("\\$\\$POSITION", "\"" + sessionInfo.getPosition() + "\"");
        result = result.replaceAll("\\$\\$OPERATOR", "\"" + sessionInfo.getOperator() + "\"");

        return result;
    }
}
